1 /**
2  * Cyclic Buffer
3  * Copyright: © 2016 Economic Modeling Specialists, Intl.
4  * Authors: Nickolay Bukreyev
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module containers.cyclicbuffer;
9 
10 private import core.exception : onRangeError;
11 private import std.experimental.allocator.mallocator : Mallocator;
12 private import std.range.primitives : empty, front, back, popFront, popBack;
13 private import containers.internal.node : shouldAddGCRange;
14 
15 /**
16  * Array that provides constant time (amortized) appending and popping
17  * at either end, as well as random access to the elements.
18  *
19  * Params:
20  *     T = the array element type
21  *     Allocator = the allocator to use. Defaults to `Mallocator`.
22  *     supportGC = true if the container should support holding references to GC-allocated memory.
23  */
24 struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T)
25 {
26 	@disable this(this);
27 
28 	private import std.conv : emplace;
29 	private import std.experimental.allocator.common : stateSize;
30 	private import std.traits : isImplicitlyConvertible, hasElaborateDestructor;
31 
32 	static if (stateSize!Allocator != 0)
33 	{
34 		/// No default construction if an allocator must be provided.
35 		@disable this();
36 
37 		/**
38 		 * Use the given `allocator` for allocations.
39 		 */
40 		this(Allocator allocator) nothrow pure @safe @nogc
41 		in
42 		{
43 			assert(allocator !is null, "Allocator must not be null");
44 		}
45 		do
46 		{
47 			this.allocator = allocator;
48 		}
49 	}
50 
51 	~this()
52 	{
53 		clear();
54 		static if (useGC)
55 		{
56 			import core.memory : GC;
57 			GC.removeRange(storage.ptr);
58 		}
59 		allocator.deallocate(storage);
60 	}
61 
62 	/**
63 	 * Removes all contents from the buffer.
64 	 */
65 	void clear()
66 	{
67 		if (!empty)
68 		{
69 			static if (hasElaborateDestructor!T)
70 			{
71 				if (start <= end)
72 					foreach (ref item; storage[start .. end + 1])
73 						.destroy(item);
74 				else
75 				{
76 					foreach (ref item; storage[start .. $])
77 						.destroy(item);
78 					foreach (ref item; storage[0 .. end + 1])
79 						.destroy(item);
80 				}
81 			}
82 			start = (end + 1) % capacity;
83 			_length = 0;
84 		}
85 	}
86 
87 	/**
88 	 * Ensures capacity is at least as large as specified.
89 	 */
90 	size_t reserve(size_t newCapacity)
91 	{
92 		immutable oldCapacity = capacity;
93 		if (newCapacity <= oldCapacity)
94 			return oldCapacity;
95 		auto old = storage;
96 		if (oldCapacity == 0)
97 			storage = cast(typeof(storage)) allocator.allocate(newCapacity * T.sizeof);
98 		else
99 		{
100 			auto a = cast(void[]) old;
101 			allocator.reallocate(a, newCapacity * T.sizeof);
102 			storage = cast(typeof(storage)) a;
103 		}
104 		static if (useGC)
105 		{
106 			import core.memory : GC;
107 			//Add, then remove. Exactly in that order.
108 			GC.addRange(storage.ptr, newCapacity * T.sizeof);
109 			GC.removeRange(old.ptr);
110 		}
111 		if (empty)
112 			end = (start - 1 + capacity) % capacity;
113 		else if (start > end)
114 		{
115 			//The buffer was wrapped around prior to reallocation.
116 
117 			//`moveEmplaceAll` is only available in 2.069+, so use a low level alternative.
118 			//Even more, we don't have to .init the moved away data, because we don't .destroy it.
119 			import core.stdc..string : memcpy, memmove;
120 			immutable prefix = end + 1;
121 			immutable suffix = oldCapacity - start;
122 			if (prefix <= suffix)
123 			{
124 				//The prefix is being moved right behind of suffix.
125 				immutable space = newCapacity - oldCapacity;
126 				if (space >= prefix)
127 				{
128 					memcpy(storage.ptr + oldCapacity, storage.ptr, prefix * T.sizeof);
129 					end += oldCapacity;
130 				}
131 				else
132 				{
133 					//There is not enough space, so move what we can,
134 					//and shift the rest to the start of the buffer.
135 					memcpy(storage.ptr + oldCapacity, storage.ptr, space * T.sizeof);
136 					end -= space;
137 					memmove(storage.ptr, storage.ptr + space, (end + 1) * T.sizeof);
138 				}
139 			}
140 			else
141 			{
142 				//The suffix is being moved forward, to the end of the buffer.
143 				//Due to the fact that these locations may overlap, use `memmove`.
144 				memmove(storage.ptr + newCapacity - suffix, storage.ptr + start, suffix * T.sizeof);
145 				start = newCapacity - suffix;
146 			}
147 			//Ensure everything is still alright.
148 			if (start <= end)
149 				assert(end + 1 - start == length);
150 			else
151 				assert(end + 1 + (newCapacity - start) == length);
152 		}
153 		return capacity;
154 	}
155 
156 	/**
157 	 * Inserts the given item into the start of the buffer.
158 	 */
159 	void insertFront(U)(U value) if (isImplicitlyConvertible!(U, T))
160 	{
161 		if (empty)
162 			reserve(4);
163 		else if ((end + 1) % capacity == start)
164 			reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2);
165 		start = (start - 1 + capacity) % capacity;
166 		_length++;
167 		emplace(&storage[start], value);
168 	}
169 
170 	/**
171 	 * Inserts the given item into the end of the buffer.
172 	 */
173 	void insertBack(U)(U value) if (isImplicitlyConvertible!(U, T))
174 	{
175 		if (empty)
176 			reserve(4);
177 		else if ((end + 1) % capacity == start)
178 			reserve(capacity >= 65_536 ? capacity + 65_536 : capacity * 2);
179 		end = (end + 1) % capacity;
180 		_length++;
181 		emplace(&storage[end], value);
182 	}
183 
184 	/// ditto
185 	alias insert = insertBack;
186 
187 	/// ditto
188 	alias insertAnywhere = insertBack;
189 
190 	/// ditto
191 	alias put = insertBack;
192 
193 	/**
194 	 * Removes the item at the start of the buffer.
195 	 */
196 	void removeFront()
197 	{
198 		version (assert) if (empty) onRangeError();
199 		size_t pos = start;
200 		start = (start + 1) % capacity;
201 		_length--;
202 		static if (hasElaborateDestructor!T)
203 			.destroy(storage[pos]);
204 	}
205 
206 	/// ditto
207 	alias popFront = removeFront;
208 
209 	/**
210 	 * Removes the item at the end of the buffer.
211 	 */
212 	void removeBack()
213 	{
214 		version (assert) if (empty) onRangeError();
215 		size_t pos = end;
216 		end = (end - 1 + capacity) % capacity;
217 		_length--;
218 		static if (hasElaborateDestructor!T)
219 			.destroy(storage[pos]);
220 	}
221 
222 	/// ditto
223 	alias popBack = removeBack;
224 
225 	/// Accesses to the item at the start of the buffer.
226 	auto ref front(this This)() nothrow pure @property @safe
227 	{
228 		version (assert) if (empty) onRangeError();
229 		alias ET = ContainerElementType!(This, T, true);
230 		return cast(ET) storage[start];
231 	}
232 
233 	/// Accesses to the item at the end of the buffer.
234 	auto ref back(this This)() nothrow pure @property @safe
235 	{
236 		version (assert) if (empty) onRangeError();
237 		alias ET = ContainerElementType!(This, T, true);
238 		return cast(ET) storage[end];
239 	}
240 
241 	/// buffer[i]
242 	auto ref opIndex(this This)(size_t i) nothrow pure @safe
243 	{
244 		version (assert) if (i >= length) onRangeError();
245 		alias ET = ContainerElementType!(This, T, true);
246 		return cast(ET) storage[(start + i) % $];
247 	}
248 
249 	/// buffer[]
250 	Range!This opIndex(this This)() nothrow pure @safe @nogc
251 	{
252 		if (empty)
253 			return typeof(return)(storage[0 .. 0], storage[0 .. 0]);
254 		if (start <= end)
255 			return typeof(return)(storage[start .. end + 1], storage[0 .. 0]);
256 		return typeof(return)(storage[start .. $], storage[0 .. end + 1]);
257 	}
258 
259 	/// buffer[i .. j]
260 	size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc
261 	{
262 		return [i, j];
263 	}
264 
265 	/// ditto
266 	Range!This opIndex(this This)(size_t[2] indices) nothrow pure @safe
267 	{
268 		size_t i = indices[0], j = indices[1];
269 		version (assert)
270 		{
271 			if (i > j) onRangeError();
272 			if (j > length) onRangeError();
273 		}
274 		if (i == j)
275 			return typeof(return)(storage[0 .. 0], storage[0 .. 0]);
276 		i = (start + i) % capacity;
277 		j = (start + j) % capacity;
278 		if (i < j)
279 			return typeof(return)(storage[i .. j], storage[0 .. 0]);
280 		return typeof(return)(storage[i .. $], storage[0 .. j]);
281 	}
282 
283 	static struct Range(ThisT)
284 	{
285 		private
286 		{
287 			static if (is(ThisT == immutable))
288 			{
289 				alias SliceT = immutable(ContainerStorageType!T)[];
290 			}
291 			else static if (is(ThisT == const))
292 			{
293 				alias SliceT = const(ContainerStorageType!T)[];
294 			}
295 			else
296 			{
297 				alias SliceT = ContainerStorageType!T[];
298 			}
299 		}
300 
301 		@disable this();
302 
303 		this(SliceT a, SliceT b) nothrow pure @safe @nogc
304 		{
305 			head = a;
306 			tail = b;
307 		}
308 
309 		This save(this This)() nothrow pure @property @safe @nogc
310 		{
311 			return this;
312 		}
313 
314 		bool empty() const nothrow pure @property @safe @nogc
315 		{
316 			return head.empty && tail.empty;
317 		}
318 
319 		size_t length() const nothrow pure @property @safe @nogc
320 		{
321 			return head.length + tail.length;
322 		}
323 
324 		alias opDollar = length;
325 
326 		auto ref front(this This)() nothrow pure @property @safe
327 		{
328 			if (!head.empty)
329 				return cast(ET) head.front;
330 			return cast(ET) tail.front;
331 		}
332 
333 		auto ref back(this This)() nothrow pure @property @safe
334 		{
335 			if (!tail.empty)
336 				return cast(ET) tail.back;
337 			return cast(ET) head.back;
338 		}
339 
340 		void popFront() nothrow pure @safe
341 		{
342 			if (head.empty)
343 			{
344 				import std.algorithm.mutation : swap;
345 				//Always try to keep `head` non-empty.
346 				swap(head, tail);
347 			}
348 			head.popFront();
349 		}
350 
351 		void popBack() nothrow pure @safe
352 		{
353 			if (!tail.empty)
354 				tail.popBack();
355 			else
356 				head.popBack();
357 		}
358 
359 		/// range[i]
360 		auto ref opIndex(this This)(size_t i) nothrow pure @safe
361 		{
362 			return cast(ET) (i < head.length ? head[i] : tail[i - head.length]);
363 		}
364 
365 		/// range[]
366 		This opIndex(this This)() nothrow pure @safe @nogc
367 		{
368 			return this.save;
369 		}
370 
371 		/// range[i .. j]
372 		size_t[2] opSlice(size_t k: 0)(size_t i, size_t j) const nothrow pure @safe @nogc
373 		{
374 			return [i, j];
375 		}
376 
377 		/// ditto
378 		This opIndex(this This)(size_t[2] indices) nothrow pure @safe
379 		{
380 			size_t i = indices[0], j = indices[1];
381 			version (assert)
382 			{
383 				if (i > j) onRangeError();
384 				if (j > length) onRangeError();
385 			}
386 			if (i >= head.length)
387 				return typeof(return)(tail[i - head.length .. j - head.length], tail[0 .. 0]);
388 			if (j <= head.length)
389 				return typeof(return)(head[i .. j], head[0 .. 0]);
390 			return typeof(return)(head[i .. $], tail[0 .. j - head.length]);
391 		}
392 
393 		/// range[...]++
394 		auto ref opUnary(string op)() nothrow pure @safe @nogc
395 		if (op == "++" || op == "--")
396 		{
397 			mixin(op ~ "head[];");
398 			mixin(op ~ "tail[];");
399 			return this;
400 		}
401 
402 		/// range[...] = value
403 		auto ref opAssign(U)(const auto ref U value) nothrow pure @safe @nogc
404 		{
405 			head[] = value;
406 			tail[] = value;
407 			return this;
408 		}
409 
410 		/// range[...] += value
411 		auto ref opOpAssign(string op, U)(const auto ref U value) nothrow pure @safe @nogc
412 		{
413 			mixin("head[] " ~ op ~ "= value;");
414 			mixin("tail[] " ~ op ~ "= value;");
415 			return this;
416 		}
417 
418 	private:
419 
420 		alias ET = ContainerElementType!(ThisT, T);
421 
422 		SliceT head, tail;
423 	}
424 
425 	/// Returns: the number of items in the buffer.
426 	size_t length() const nothrow pure @property @safe @nogc { return _length; }
427 
428 	/// ditto
429 	alias opDollar = length;
430 
431 	/// Returns: maximal number of items the buffer can hold without reallocation.
432 	size_t capacity() const nothrow pure @property @safe @nogc { return storage.length; }
433 
434 	/// Returns: whether or not the CyclicBuffer is empty.
435 	bool empty() const nothrow pure @property @safe @nogc { return length == 0; }
436 
437 private:
438 
439 	import containers.internal.storage_type : ContainerStorageType;
440 	import containers.internal.element_type : ContainerElementType;
441 	import containers.internal.mixins : AllocatorState;
442 
443 	enum bool useGC = supportGC && shouldAddGCRange!T;
444 	mixin AllocatorState!Allocator;
445 	ContainerStorageType!T[] storage;
446 	size_t start, end, _length;
447 }
448 
449 version(emsi_containers_unittest) private
450 {
451 	import std.algorithm.comparison : equal;
452 	import std.experimental.allocator.gc_allocator : GCAllocator;
453 	import std.experimental.allocator.building_blocks.free_list : FreeList;
454 	import std.range : iota, lockstep, StoppingPolicy;
455 
456 	struct S
457 	{
458 		int* a;
459 
460 		~this()
461 		{
462 			(*a)++;
463 		}
464 	}
465 
466 	class C
467 	{
468 		int* a;
469 
470 		this(int* a)
471 		{
472 			this.a = a;
473 		}
474 
475 		~this()
476 		{
477 			(*a)++;
478 		}
479 	}
480 }
481 
482 version(emsi_containers_unittest) unittest
483 {
484 	static void test(int size)
485 	{
486 		{
487 			CyclicBuffer!int b;
488 			assert(b.empty);
489 			foreach (i; 0 .. size)
490 			{
491 				assert(b.length == i);
492 				b.insertBack(i);
493 				assert(b.back == i);
494 			}
495 			assert(b.length == size);
496 			foreach (i; 0 .. size)
497 			{
498 				assert(b.length == size - i);
499 				assert(b.front == i);
500 				b.removeFront();
501 			}
502 			assert(b.empty);
503 		}
504 		{
505 			CyclicBuffer!int b;
506 			foreach (i; 0 .. size)
507 			{
508 				assert(b.length == i);
509 				b.insertFront(i);
510 				assert(b.front == i);
511 			}
512 			assert(b.length == size);
513 			foreach (i; 0 .. size)
514 			{
515 				assert(b.length == size - i);
516 				assert(b.back == i);
517 				b.removeBack();
518 			}
519 			assert(b.empty);
520 		}
521 	}
522 
523 	foreach (size; [1, 2, 3, 4, 5, 7, 8, 9, 512, 520, 0x10000, 0x10001, 0x20000])
524 		test(size);
525 }
526 
527 version(emsi_containers_unittest) unittest
528 {
529 	static void test(int prefix, int suffix, int newSize)
530 	{
531 		CyclicBuffer!int b;
532 		foreach_reverse (i; 0 .. suffix)
533 			b.insertFront(i);
534 		foreach (i; suffix .. suffix + prefix)
535 			b.insertBack(i);
536 		assert(b.length == prefix + suffix);
537 		b.reserve(newSize);
538 		assert(b.length == prefix + suffix);
539 		assert(equal(b[], iota(prefix + suffix)));
540 	}
541 
542 	immutable prefixes = [2,  3,  3, 4, 4];
543 	immutable suffixes = [3,  2,  4, 3, 4];
544 	immutable sizes    = [16, 16, 9, 9, 9];
545 
546 	foreach (a, b, c; lockstep(prefixes, suffixes, sizes, StoppingPolicy.requireSameLength))
547 		test(a, b, c);
548 }
549 
550 version(emsi_containers_unittest) unittest
551 {
552 	int* a = new int;
553 	{
554 		CyclicBuffer!S b;
555 		{
556 			S s = { a };
557 			foreach (i; 0 .. 5)
558 				b.insertBack(s);
559 			assert(*a == 5);
560 			foreach (i; 0 .. 5)
561 				b.insertBack(S(a));
562 			assert(*a == 10);
563 			foreach (i; 0 .. 5)
564 			{
565 				b.removeBack();
566 				b.removeFront();
567 			}
568 			assert(*a == 20);
569 		}
570 		assert(*a == 21);
571 	}
572 	assert(*a == 21);
573 }
574 
575 version(emsi_containers_unittest) unittest
576 {
577 	int* a = new int;
578 	CyclicBuffer!C b;
579 	{
580 		C c = new C(a);
581 		foreach (i; 0 .. 10)
582 			b.insertBack(c);
583 		assert(*a == 0);
584 		foreach (i; 0 .. 5)
585 		{
586 			b.removeBack();
587 			b.removeFront();
588 		}
589 		foreach (i; 0 .. b.capacity)
590 			b.insertFront(null);
591 		assert(*a == 0);
592 	}
593 	string s = "";
594 	foreach (i; 0 .. 1_000)
595 		s = s ~ 'a';
596 	s = "";
597 	import core.memory : GC;
598 	GC.collect();
599 	assert(*a == 0 || *a == 1);
600 }
601 
602 version(emsi_containers_unittest) unittest
603 {
604 	CyclicBuffer!int b;
605 	b.insertFront(10);
606 	assert(b[0] == 10);
607 	b.insertFront(20);
608 	assert(b[0] == 20);
609 	assert(b[1] == 10);
610 	b.insertFront(30);
611 	assert(b[0] == 30);
612 	assert(b[1] == 20);
613 	assert(b[2] == 10);
614 	b.insertBack(5);
615 	assert(b[0] == 30);
616 	assert(b[1] == 20);
617 	assert(b[2] == 10);
618 	assert(b[3] == 5);
619 	b.back = 7;
620 	assert(b[3] == 7);
621 }
622 
623 version(emsi_containers_unittest) unittest
624 {
625 	import std.range : isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange;
626 	CyclicBuffer!int b;
627 	static assert(isInputRange!(typeof(b[])));
628 	static assert(isForwardRange!(typeof(b[])));
629 	static assert(isBidirectionalRange!(typeof(b[])));
630 	static assert(isRandomAccessRange!(typeof(b[])));
631 }
632 
633 version(emsi_containers_unittest) unittest
634 {
635 	CyclicBuffer!int b;
636 	assert(b[].empty);
637 }
638 
639 version(emsi_containers_unittest) unittest
640 {
641 	FreeList!(Mallocator, 0, 64) alloc;
642 	FreeList!(GCAllocator, 0, 64) alloc2;
643 	auto b = CyclicBuffer!(int, typeof(&alloc))(&alloc);
644 	auto b2 = CyclicBuffer!(int, typeof(&alloc2))(&alloc2);
645 	auto b3 = CyclicBuffer!(int, GCAllocator)();
646 }
647 
648 version(emsi_containers_unittest) unittest
649 {
650 	static void testConst(const ref CyclicBuffer!int b, int x)
651 	{
652 		assert(b[0] == x);
653 		assert(b.front == x);
654 		static assert(!__traits(compiles, { ++b[0]; } ));
655 		assert(equal(b[], [x]));
656 	}
657 
658 	CyclicBuffer!int b;
659 	b.insertFront(0);
660 	assert(b.front == 0);
661 	b.front++;
662 	assert(b[0] == 1);
663 	b[0]++;
664 	++b[0];
665 	assert(b.front == 3);
666 	assert(!b.empty);
667 	b[0] *= 2;
668 	assert(b[0] == 6);
669 	testConst(b, 6);
670 	b[]++;
671 	assert(equal(b[], [7]));
672 	b[0] = 5;
673 	assert(b[0] == 5);
674 	assert(b.front == 5);
675 	testConst(b, 5);
676 	assert(b[][0] == 5);
677 }
678 
679 version(emsi_containers_unittest) unittest
680 {
681 	int* a = new int;
682 	{
683 		CyclicBuffer!S b;
684 		foreach (i; 0 .. 5)
685 			b.insertBack(S(a));
686 		assert(*a == 5);
687 	}
688 	assert(*a == 10);
689 	*a = 0;
690 	{
691 		CyclicBuffer!S b;
692 		foreach (i; 0 .. 4)
693 			b.insertBack(S(a));
694 		assert(*a == 4);
695 		b.removeFront();
696 		assert(*a == 5);
697 		b.insertBack(S(a));
698 		assert(*a == 6);
699 	}
700 	assert(*a == 10);
701 }
702 
703 version(emsi_containers_unittest) unittest
704 {
705 	CyclicBuffer!int b;
706 	foreach (i; 0 .. 4)
707 		b.insertBack(i);
708 	b.removeFront();
709 	b.removeFront();
710 	b.insertBack(4);
711 	b.insertBack(5);
712 	assert(equal(b[], [2, 3, 4, 5]));
713 	b.reserve(5);
714 	assert(equal(b[], [2, 3, 4, 5]));
715 }
716 
717 version(emsi_containers_unittest) unittest
718 {
719 	CyclicBuffer!int b;
720 	foreach (i; 0 .. 4)
721 		b.insertBack(i);
722 	b.removeFront();
723 	b.removeFront();
724 	b.removeFront();
725 	b.insertBack(4);
726 	b.insertBack(5);
727 	b.insertBack(6);
728 	assert(equal(b[], [3, 4, 5, 6]));
729 	b.reserve(5);
730 	assert(equal(b[], [3, 4, 5, 6]));
731 }
732 
733 version(emsi_containers_unittest) unittest
734 {
735 	static void test(ref CyclicBuffer!int b)
736 	{
737 		assert(equal(b[], [4, 5, 6, 7, 8, 9, 10, 11]));
738 		assert(b[3 .. 3].empty);
739 		auto slice = b[1 .. 6];
740 		assert(equal(slice, [5, 6, 7, 8, 9]));
741 		slice[3 .. 5] = 0;
742 		assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11]));
743 		slice[0 .. 2] += 1;
744 		assert(equal(b[], [4, 6, 7, 7, 0, 0, 10, 11]));
745 		slice[0 .. 2]--;
746 		assert(equal(b[], [4, 5, 6, 7, 0, 0, 10, 11]));
747 		auto copy = slice.save;
748 		assert(equal(slice, copy));
749 		assert(equal(slice, copy[]));
750 		assert(slice.back == 0);
751 		slice.popBack();
752 		assert(equal(slice, [5, 6, 7, 0]));
753 		assert(slice.back == 0);
754 		slice.popBack();
755 		assert(equal(slice, [5, 6, 7]));
756 		assert(slice.back == 7);
757 		slice.popBack();
758 		assert(equal(slice, [5, 6]));
759 		assert(equal(copy, [5, 6, 7, 0, 0]));
760 		slice[1] = 10;
761 		assert(-copy[1] == -10);
762 		copy[1] *= 2;
763 		assert(slice[1] == 20);
764 		assert(b[2] == 20);
765 		auto copy2 = copy[0 .. $];
766 		assert(equal(copy, copy2));
767 	}
768 
769 	{
770 		CyclicBuffer!int b;
771 		foreach (i; 4 .. 12)
772 			b.insertBack(i);
773 		test(b);
774 	}
775 	{
776 		CyclicBuffer!int b;
777 		foreach (i; 0 .. 8)
778 			b.insertBack(i);
779 		foreach (i; 0 .. 4)
780 			b.removeFront();
781 		foreach (i; 8 .. 12)
782 			b.insertBack(i);
783 		test(b);
784 	}
785 }
786 
787 version(emsi_containers_unittest) unittest
788 {
789 	CyclicBuffer!int b;
790 	foreach (i; 0 .. 10)
791 		b.insertBack(i);
792 	assert(b.capacity >= 10);
793 	b.reserve(12);
794 	assert(b.capacity >= 12);
795 }
796 
797 version(emsi_containers_unittest) unittest
798 {
799 	CyclicBuffer!int b;
800 	foreach (i; 0 .. 6)
801 		b.insertBack(i);
802 	foreach (i; 6 .. 8)
803 		b.insertFront(i);
804 	assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5]));
805 	b.reserve(b.capacity + 1);
806 	assert(equal(b[], [7, 6, 0, 1, 2, 3, 4, 5]));
807 }
808 
809 version(emsi_containers_unittest) unittest
810 {
811     static class Foo
812     {
813         string name;
814     }
815 
816     CyclicBuffer!Foo b;
817 }